//Implement Kraw::Display using OpenGL

#include "kraw.h"

#include "GLDisplay.h"

#include <Windows.h>

using namespace Kraw;

//--------------------------------------------------------------------------------
//TEMP- copied from the Humus framework

#ifdef _WIN32

void initEntryPoints(HWND hwnd, const PIXELFORMATDESCRIPTOR &pfd)
{
	HDC hdc = GetDC(hwnd);

	int nPixelFormat = ChoosePixelFormat(hdc, &pfd);
	SetPixelFormat(hdc, nPixelFormat, &pfd);

	HGLRC hglrc = wglCreateContext(hdc);
	wglMakeCurrent(hdc, hglrc);

	initExtensions(hdc);

	wglMakeCurrent(NULL, NULL);
	wglDeleteContext(hglrc);
	ReleaseDC(hwnd, hdc);
}

LRESULT CALLBACK PFWinProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	return DefWindowProc(hwnd, message, wParam, lParam);
};

bool GLDisplay::initCaps()
{
	PIXELFORMATDESCRIPTOR pfd = {
        sizeof (PIXELFORMATDESCRIPTOR), 1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA, this->colorBits,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		this->depthBits, this->stencilBits,
		0, PFD_MAIN_PLANE, 0, 0, 0, 0
    };

    HINSTANCE hInstance = GetModuleHandle( NULL );

	WNDCLASS wincl;
	wincl.hInstance = hInstance;
	wincl.lpszClassName = "PFrmt";
	wincl.lpfnWndProc = PFWinProc;
	wincl.style = 0;
	wincl.hIcon = NULL;
	wincl.hCursor = NULL;
	wincl.lpszMenuName = NULL;
	wincl.cbClsExtra = 0;
	wincl.cbWndExtra = 0;
	wincl.hbrBackground = NULL;
	RegisterClass(&wincl);

	HWND hPFwnd = CreateWindow("PFrmt", "PFormat", WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 8, 8, HWND_DESKTOP, NULL, hInstance, NULL);
	initEntryPoints(hPFwnd, pfd);
	SendMessage(hPFwnd, WM_CLOSE, 0, 0);

	return true;
}

MONITORINFO monInfo;

BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData){
	if (*(int *) dwData == 0){
		monInfo.cbSize = sizeof(monInfo);
		GetMonitorInfo(hMonitor, &monInfo);
		return FALSE;
	}
	(*(int *) dwData)--;

	return TRUE;
}

bool GLDisplay::initAPI()
{
    int screen = 0;

	if (screen >= GetSystemMetrics(SM_CMONITORS)) this->screenIndex = 0;

	int monitorCounter = screen;
	EnumDisplayMonitors(NULL, NULL, MonitorEnumProc, (LPARAM) &monitorCounter);

	DWORD flags = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
	int x, y;

	x = monInfo.rcMonitor.left;
	y = monInfo.rcMonitor.top;

	device.cb = sizeof(device);
	EnumDisplayDevices(NULL, screen, &device, 0);

	DEVMODE dm;//, tdm;
	memset(&dm, 0, sizeof(dm));
	dm.dmSize = sizeof(dm);
	dm.dmBitsPerPel = colorBits;
	dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
    dm.dmPelsWidth  = width;//fullscreenWidth;
	dm.dmPelsHeight = height;//fullscreenHeight;
	dm.dmDisplayFrequency = 60;

	// Find a suitable fullscreen format
	int i = 0;
	int targetHz = 85;
	//char str[128];

	if (bFullscreen){
		if (ChangeDisplaySettingsEx((const char *) device.DeviceName, &dm, NULL, CDS_FULLSCREEN, NULL) == DISP_CHANGE_SUCCESSFUL){
			flags |= WS_POPUP;
			//captureMouse( true );
		} else {
			//ErrorMsg("Couldn't set fullscreen mode");
			bFullscreen = false;
		}
	}

//	sprintf(str, "%s (%dx%d)", getTitle(), width, height);
	if (!bFullscreen){
		flags |= WS_OVERLAPPEDWINDOW;

		RECT wRect;
		wRect.left = 0;
		wRect.right = width;
		wRect.top = 0;
		wRect.bottom = height;
		AdjustWindowRect(&wRect, flags, FALSE);

		width  = min(wRect.right  - wRect.left, monInfo.rcWork.right  - monInfo.rcWork.left);
		height = min(wRect.bottom - wRect.top,  monInfo.rcWork.bottom - monInfo.rcWork.top);

		x = (monInfo.rcWork.left + monInfo.rcWork.right  - width ) / 2;
		y = (monInfo.rcWork.top  + monInfo.rcWork.bottom - height) / 2;
	}

    hwnd = CreateWindow("Kraw::GLDisplay", "Kraw::GLDisplay Display", flags, x, y, width, height, HWND_DESKTOP, NULL, hInstance, NULL);

	PIXELFORMATDESCRIPTOR pfd = {
        sizeof (PIXELFORMATDESCRIPTOR), 1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA, colorBits,
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		depthBits, stencilBits,
		0, PFD_MAIN_PLANE, 0, 0, 0, 0
    };

	hdc = GetDC(hwnd);

	int iAttribs[] = {
		WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
		WGL_ACCELERATION_ARB,   WGL_FULL_ACCELERATION_ARB,
		WGL_DOUBLE_BUFFER_ARB,  GL_TRUE,
		WGL_RED_BITS_ARB,       8,
		WGL_GREEN_BITS_ARB,     8,
		WGL_BLUE_BITS_ARB,      8,
		WGL_ALPHA_BITS_ARB,     (colorBits > 24)? 8 : 0,
		WGL_DEPTH_BITS_ARB,     depthBits,
		WGL_STENCIL_BITS_ARB,   stencilBits,
		0
	};

	int pixelFormats[256];
	int bestFormat = 0;
	int bestSamples = 0;
	uint nPFormats;
	if (WGL_ARB_pixel_format_supported && wglChoosePixelFormatARB(hdc, iAttribs, NULL, elementsOf(pixelFormats), pixelFormats, &nPFormats) && nPFormats > 0){
		int minDiff = 0x7FFFFFFF;
		int attrib = WGL_SAMPLES_ARB;
		int samples;

		// Find a multisample format as close as possible to the requested
		for (uint i = 0; i < nPFormats; i++){
			wglGetPixelFormatAttribivARB(hdc, pixelFormats[i], 0, 1, &attrib, &samples);
			int diff = abs(antiAliasSamples - samples);
			if (diff < minDiff){
				minDiff = diff;
				bestFormat = i;
				bestSamples = samples;
			}
		}
	} else {
		pixelFormats[0] = ChoosePixelFormat(hdc, &pfd);
	}

	antiAliasSamples = bestSamples;

	SetPixelFormat(hdc, pixelFormats[bestFormat], &pfd);

	glContext = wglCreateContext(hdc);
	wglMakeCurrent(hdc, glContext);

	initExtensions(hdc);

	if (WGL_ARB_multisample_supported && GL_ARB_multisample_supported && antiAliasSamples > 0){
		glEnable(GL_MULTISAMPLE_ARB);
	}

//	if (bFullscreen) captureMouse(!configDialog->isVisible());

	//renderer = new OpenGLRenderer(hdc, glContext);
	//renderer->setViewport(width, height);

	return true;
}

void GLDisplay::exitAPI()
{
//	delete renderer;

	wglMakeCurrent(NULL, NULL);
	wglDeleteContext(glContext);
	ReleaseDC(hwnd, hdc);

	DestroyWindow(hwnd);

	if (bFullscreen){
		// Reset display mode to default
		ChangeDisplaySettingsEx((const char *) device.DeviceName, NULL, NULL, 0, NULL);
	}
}

#endif

//--------------------------------------------------------------------------------

LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    //get the display* from the user data
    GLDisplay* display = (GLDisplay*) GetWindowLong( hWnd, GWL_USERDATA );
    Input* input = Input::Get();
    
    switch (msg)
    {
        case WM_DESTROY:
            display->OnWindowDestroy();
            PostQuitMessage( 0 );
            return 0;

        case WM_PAINT:
            //Render();
            ValidateRect( hWnd, NULL );
            return 0;

        //handle input from mouse
        case WM_MOUSEMOVE:
            {
                short x = LOWORD(lParam);
                short y = HIWORD(lParam);
                input->mouse_x = x;
                input->mouse_y = y;
                input->relmouse_x = x / display->width;
                input->relmouse_y = y / display->height;
            }
            break;
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );
}

//--------------------------------------------------------------------------------

void GLDisplay::OnWindowDestroy()
{
    //close the display before the window is destroyed
    this->bIsQuitRequested = true;
}

//--------------------------------------------------------------------------------

bool GLDisplay::OpenWindow()
{
    this->hInstance = GetModuleHandle(NULL);

	WNDCLASS wincl;
	wincl.hInstance = hInstance;
    wincl.lpszClassName = "Kraw::GLDisplay";
	wincl.lpfnWndProc = WndProc;
	wincl.style = 0;
	wincl.hIcon = NULL;//LoadIcon(hThisInst, MAKEINTRESOURCE(IDI_MAINICON));
	wincl.hCursor = NULL;//LoadCursor(NULL, IDI_APPLICATION);
	wincl.lpszMenuName = NULL;
	wincl.cbClsExtra = 0;
	wincl.cbWndExtra = 0;
	wincl.hbrBackground = NULL;

	if (!RegisterClass(&wincl))
        return false;

    return true;
}

//--------------------------------------------------------------------------------

void GLDisplay::CloseWindow()
{
}

//--------------------------------------------------------------------------------

static int numDisplays = 0;

Display* Display::Create()
{
    GLDisplay* display = new GLDisplay;
    if (numDisplays == 0)
    {
        display->initCaps();
    	display->OpenWindow();//TEMP- register window class
    }

    numDisplays++;
    return display;
}

//--------------------------------------------------------------------------------

GLDisplay::GLDisplay()
{
    glContext = NULL;
}

//--------------------------------------------------------------------------------

GLDisplay::~GLDisplay()
{
    this->Close();
    numDisplays--;
}

//--------------------------------------------------------------------------------

bool Display::Open()
{
    ASSERT(!this->bIsOpen);
    GLDisplay* self = (GLDisplay*) this;

    if (self->initAPI())
    {
        this->bIsOpen = true;

        SetWindowLong( self->hwnd, GWL_USERDATA, (LONG) self );
        ShowWindow( self->hwnd, SW_SHOWDEFAULT );
        UpdateWindow( self->hwnd );
    }

    return bIsOpen;
}

//--------------------------------------------------------------------------------

void Display::Close()
{
    GLDisplay* self = (GLDisplay*) this;
    self->exitAPI();
}

//--------------------------------------------------------------------------------

bool Display::Update()
{
    GLDisplay* self = (GLDisplay*) this;

    InvalidateRect( self->hwnd, NULL, FALSE );

    MSG msg;
    ZeroMemory( &msg, sizeof( msg ) );
    while ( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
    {
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}
	
	if ( msg.message == WM_QUIT )
	{
		self->bIsQuitRequested = true;
	}
    
    return !self->bIsQuitRequested;
}

//--------------------------------------------------------------------------------

bool Display::BeginDraw()
{
    glColor3f(1, 1, 1);

    glClearColor(0,0,1,1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    return true;
}

void Display::EndDraw()
{
    GLDisplay* self = (GLDisplay*) this;
#ifdef _WIN32
	if (WGL_EXT_swap_control_supported){
		wglSwapIntervalEXT(bVerticalSync ? 1 : 0);
	}

    SwapBuffers(self->hdc);
#endif
}

//move to GLMesh.h
Mesh* Display::LoadMesh( MeshSource* src )
{
    //empty
    return NULL;
}

bool Display::DrawMesh( Mesh* msh )
{
    //empty
    return true;
}

//move to GLTransform.h
void Display::SetTransform( Display::TransformType type, const matrix44& mat )
{
    //empty
}
